package pipeline;

import baseObjects.Language;
import corpus.Observable;
import corpus.Observer;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.text.NumberFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

public class KeyboardConsumer extends Thread implements Observable {

    public static final String TOP = "TOP";
    public static final String CALCULATED = "CALCULATED";
    public static final String FINISH = "FINISH";

    protected Vector<Observer> observers = null;
    protected SynchronizedBuffer keyboards = new SynchronizedBuffer();
    protected TopBuffer topBuffer;
//    protected KeyboardCandidate[] top = null;
//    protected int topPlace;
    protected String corpusDir = null;
    protected Language lang = null;
    protected Character lastChar = null;
    protected Map<Integer, Integer> distance = null;


    //TODO: change the numbers:
    final protected int SHIFT = 23;
    final protected int CTRL = 23;
    final protected int ALT = 23;

    final protected int[][] distances =
        {{19, 00, 00, 00, 00, 19, 19, 00, 00, 00, 00, 19, 37},
         {19, 19, 19, 19, 24, 31, 19, 19, 19, 19, 25, 39, 57},
         {00, 00, 00, 00, 19, 19, 00, 00, 00, 00, 19},
         {21, 21, 21, 21, 34, 22, 21, 21, 21, 21}};

//    Logger logger = Logger.getLogger(this.getClass().getName());
    Logger myLogger = Logger.getLogger(this.getClass().getName()+this.getName());
    private boolean go = true;

    public KeyboardConsumer(SynchronizedBuffer key,TopBuffer topB, String corpusDir, Language l) {
        FileHandler fh,fh2;
        try{
//            fh = new FileHandler(this.getClass().getName(), 100 * 1024 * 1024, 1, false);
            fh2 = new FileHandler(this.getClass().getName(), 500 * 1024 * 1024, 2, true);
//            logger.addHandler(fh);
            myLogger.addHandler(fh2);
//            logger.setLevel(Level.ALL);
            myLogger.setLevel(Level.ALL);
//            fh.setFormatter(new SimpleFormatter());
            fh2.setFormatter(new SimpleFormatter());
        }catch(Throwable t){
            System.err.println(t.getMessage());
        }
        this.corpusDir = corpusDir;
        this.topBuffer = topB;
        this.lang = l;
        observers = new Vector<Observer>();
        keyboards = key;
//        top = new KeyboardCandidate[topCount];
//        topPlace = 0;
        //open mapping.properties to get keyCode->distance map
        Properties p = new Properties();
        try {
            p.load(new FileInputStream("mapping.properties"));
            distance= new HashMap<Integer, Integer>();
            for (Object kCode : p.keySet()) {
                distance.put(new Integer(((String)kCode).trim()), new Integer(((String)p.get(kCode)).trim()));
            }
        } catch (FileNotFoundException ex) {
            updateAll("mapping.properties not found "+ex.getMessage());
        }catch ( IOException ex){
            updateAll("ioExceptopn "+ex.getMessage());
        }
        System.out.println("KeyboardConsumer Ctor end");
    }




    protected void consume() {
        KeyboardCandidate key = keyboards.remove();
        updateAll(this.getName() + " consuming keyboard: " + key.getId());
        //updateAll(printKeyBoard(key));
//        long time = System.currentTimeMillis();
//        updateAll("start time (mili) = "+time);
        double dist = calculateDistance(key);
//        System.out.println("time to calc: "+ TimeUnit.SECONDS.convert(System.currentTimeMillis()-time,TimeUnit.MILLISECONDS)+"seconds");
//        updateAll("distance for this keyboard("+key.getId()+") = "+dist);
        key.setScore(dist);
//        logger.log(Level.ALL, NumberFormat.getNumberInstance().format(key.getId()) + "(" + key.getScore() + "):\n" + printKeyBoard(key));
        myLogger.log(Level.ALL, NumberFormat.getNumberInstance().format(key.getId()) + "(" + key.getScore() + "):\n" + printKeyBoard(key));

        //        System.err.println(">>>> STOP!!!");
//        new Scanner(System.in).next();
        //update TOP collection:
        updateAll(CALCULATED+":"+key.getId());
        if (dist<topBuffer.getBiggestScore()){
            topBuffer.add(key);
        }

    }


    @Override
    public void run() {

//        System.err.println("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
//        updateAll("i am running");
        while (go) {
//            updateAll("consuming");
//            if (keyboards.size()!=0){
            this.consume();
//
//            }
//            else
//                updateAll("buffer empty");
        }
        while (this.keyboards.size() >0){
            this.consume();
        }
        updateAll(FINISH);
        unlockFiles();
    }

    private void unlockFiles(){
//        for(Handler h: logger.getHandlers())
//            h.close();
        for(Handler h : myLogger.getHandlers())
            h.close();
    }

    public void stopRun(){
        this.go = false;
        unlockFiles();
    }
    private double calculateDistance(KeyboardCandidate key) {
        double ans = 0.0;
        File files = new File(corpusDir);
        if (!files.exists()) {
            updateAll("Directory Not Exist or cannot be opend");
        } else {

        if (files.isDirectory()) {
            ans += readDir(files, key);
        } else if (files.isFile()) {
            ans += readFile(files, key);
        } else {
            updateAll("INDEXER : error not a file or directory");
            }
        }
        return ans;
    }

    private double readDir(File dir, KeyboardCandidate key) {
        double ans = 0;
//        updateAll("reading Directory " + dir.getAbsolutePath());
        File[] fileList = dir.listFiles();
        for (File f : fileList) {
            if (f.isDirectory()) {
                if (f.getName().charAt(0) != '.' && !f.isHidden()) {
                    ans += readDir(f, key);
                }
            } else {
                if (!f.isHidden()) {
                    ans += readFile(f, key);
                }
            }
        }
        return ans;
    }

    private double readFile(File file, KeyboardCandidate key) {
        double ans = 0;
        BufferedReader br;
        try {
//            Vector<Symbol> symbols = lang.getSymbolsVect();

            br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF8"));
            if (!file.getName().equals("corpus.properties")) {
//                updateAll("reading file " + file.getName() + " size: " + file.length() / 1024 + " Kb");
//                while ((line = br.readLine()) != null) {
//                    int charNum = 0;
//                    for (Character c : line.toCharArray()) {
                int chr;
                while((chr = br.read())!=-1){
                    char c = (char) chr;
                        //updateAll("checking char: " + c);
                        if (lang.contains(c)) {
//                            this.supportedCharCount++;
                            ans += calcCharDist(c, key);
                        } else if (!Character.isWhitespace(c) && !Character.isSpaceChar(c)) {
//                            updateAll("the char " + c + "(" + charNum + ") in line " + lineNUm + " is not supported");
                        }

                }
//                updateAll(file.getPath() + " finished");
            }
            br.close();
        } catch (UnsupportedEncodingException ex) {
            updateAll("unsuported Encoding : " + ex.getMessage());
        } catch (FileNotFoundException ex) {
            updateAll("file not found : " + ex.getMessage());
        } catch (IOException ex) {
            updateAll("IOexception while reading file: " + ex.getMessage());
        }

        return ans;
    }

    private double calcCharDist(Character c, KeyboardCandidate key) {
        double ans = 0;
        if (this.lastChar == null) {
            this.lastChar = c;
            return 0.0;
        } else {
            int keyCode = key.getKeyCode(c);
//            updateAll(">>>> keyCode for " + c + "= " + keyCode);

            int codePrefix = keyCode/1000;
//            updateAll(">>>> keycode prefix : "+codePrefix);

            //base to key
            ans = distance.get(keyCode);
//            updateAll("distance base -> key : "+ans);
            //back to base:
            ans += distance.get(keyCode);
//            updateAll("distance base->key->base : "+ans);

            switch (codePrefix){
                case 1:
                    ans += SHIFT;
//                    updateAll("shift panelty!");
                    break;
                case 2:
                    ans += CTRL;
//                    updateAll("ctrl panelty");
                    break;
                case 3:
                    ans += ALT;
//                    updateAll("alt panelty");
                    break;
            }

//            updateAll("the final distance is: "+ ans);
//            updateAll(key.toString());
            this.lastChar = c;
        }

        return ans;
    }

    public void addObserver(Observer o) {
        observers.add(o);
    }

    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    public void updateAll(String s) {
        for (Observer observer : observers) {
            observer.recive(this, s);
        }
    }

   private String printKeyBoard(KeyboardCandidate k) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < k.getKeys().length; i++) {
            sb.append("||");
            for (int j = 0; j < k.getKeys()[i].length; j++) {
                sb.append(k.getKeys()[i][j].getRegular() + "||");
            }
            sb.append("\n");
        }
        return sb.toString();
    }




}
